using System;
using System.Collections;
using System.Collections.Generic;

namespace Kopernikus.Collections.Generic
{
    public class Set<T> : IEnumerable<T>, IEquatable<Set<T>>
    {
        private readonly Dictionary<T, T> m_members = new Dictionary<T, T>();
        private int? m_hashValue;

        public Set()
        {
        }

        public Set(IEnumerable<T> items)
        {
            Add(items);
        }

        public int Count
        {
            get { return m_members.Count; }
        }

        #region IEnumerable<T> Members

        public IEnumerator<T> GetEnumerator()
        {
            return m_members.Keys.GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return m_members.Keys.GetEnumerator();
        }

        #endregion

        #region IEquatable<Set<T>> Members

        public bool Equals(Set<T> other)
        {
            if (ReferenceEquals(this, other))
            {
                return true;
            }

            if ((m_hashValue.HasValue && other.m_hashValue.HasValue) &&
                !m_hashValue.Value.Equals(other.m_hashValue.Value))
            {
                return false;
            }

            if (!Count.Equals(other.Count))
            {
                return false;
            }
            foreach (T key in m_members.Keys)
            {
                if (!other.m_members.ContainsKey(key))
                {
                    return false;
                }
            }
            return true;
        }

        #region Object overrides

        public override string ToString()
        {
            //return string.Concat("Set<", typeof (T).Name, "> = {",
            return string.Concat("{",
                                 string.Join(",",
                                             //string.Join(Environment.NewLine,
                                             new List<T>(m_members.Keys).ConvertAll<string>(delegate(T member)
                                                                                                {
                                                                                                    return
                                                                                                        ReferenceEquals
                                                                                                            (null,
                                                                                                             member)
                                                                                                            ? "(null)"
                                                                                                            : member.
                                                                                                                  ToString
                                                                                                                  ();
                                                                                                }).ToArray()), "}");
        }

        public override bool Equals(object obj)
        {
            return Equals(obj as Set<T>);
        }

        public override int GetHashCode()
        {
            if (!m_hashValue.HasValue)
            {
                int h = 0;
                foreach (T key in m_members.Keys)
                {
                    h ^= key.GetHashCode();
                }
                m_hashValue = h;
            }
            return m_hashValue.Value;
        }

        #endregion

        #endregion

        public Set<T> Union(Set<T> other)
        {
            Set<T> union = new Set<T>();
            union.Add(this);
            union.Add(other);
            return union;
        }

        public T Add(T item)
        {
            T added;
            if (m_members.TryGetValue(item, out added))
            {
                return added;
            }
            m_hashValue = null;
            m_members.Add(item, item);
            return item;
        }

        public bool Add(IEnumerable<T> items)
        {
            bool added = false;
            foreach (T item in items)
            {
                if (!m_members.ContainsKey(item))
                {
                    m_hashValue = null;
                    m_members.Add(item, item);
                    added = true;
                }
            }
            return added;
        }

        public bool Remove(T item)
        {
            if (m_members.Remove(item))
            {
                m_hashValue = null;
                return true;
            }
            return false;
        }

        public void Remove(IEnumerable<T> items)
        {
            foreach (T item in items)
            {
                if (m_members.Remove(item))
                {
                    m_hashValue = null;
                }
            }
        }

        public bool Contains(T item)
        {
            return m_members.ContainsKey(item);
        }

        public bool Contains(IEnumerable<T> items)
        {
            foreach (T item in items)
            {
                if (!m_members.ContainsKey(item))
                {
                    return false;
                }
            }
            return true;
        }

        public bool ContainsAny(IEnumerable<T> items)
        {
            foreach (T item in items)
            {
                if (m_members.ContainsKey(item))
                {
                    return true;
                }
            }
            return false;
        }


        public void Each(Action<T> action)
        {
            foreach (T item in m_members.Keys)
            {
                action(item);
            }
        }
    }
}